---
title: Business rules
summary: Business rules enforced by DBConnectionAPI while composing, validating, caching and disclosing database connection strings.
status: draft
owner: Architecture
last_reviewed: 2026-10-09
---

# Business rules

DBConnectionAPI composes database connection strings for authorized callers from templates and secure credentials held in the SAIF Environment API, validates Oracle and SQL Server strings with a connectivity probe, caches validated strings in memory, and exposes an Admin surface that returns the raw credential parts. The 25 rules below cover caching, key construction, template and credential handling, probe and retry behavior, error-to-status mapping, audit logging, Admin disclosure and authorization. Rules are numbered [BR-DBC-001](#br-dbc-001-serve-from-the-cache-when-the-key-is-present) to [BR-DBC-025](#br-dbc-025-resource-authorization-on-every-endpoint). Each rule heading combines the rule identifier and its title.

---

## BR-DBC-001 Serve from the cache when the key is present

| Field | Value |
| --- | --- |
| Title | Serve from the cache when the key is present |
| Statement | On every retrieval request the Environment API template is read and formatted first. A connection string is then served from the in-memory cache when its key is already present. Secure-credential retrieval, credential validation, the connectivity probe and cache insertion run only on a cache miss |
| Trigger | Every retrieval request, in the service layer after the template lookup and before the secure-credential retrieval |
| Enforcement | `ConnectionManager.GetOracleConnectionString`, `GetSqlConnectionString`, `GetPostgreSqlConnectionString` (`HasConnection`, `GetCachedConnection`) |
| Origin | api |
| Reached from | [`GET /v1/Connections/SQL/{server}/{instance}/{username}`](endpoints/connections.md#get-v1-connections-sql-server-instance-username), [`GET /v1/Connections/PostgreSQL/{instance}/{username}`](endpoints/connections.md#get-v1-connections-postgresql-instance-username), [`GET /v1/Connections/ORA/{instance}/{username}`](endpoints/connections.md#get-v1-connections-ora-instance-username) |
| Outcome | Cache hit: the template lookup still occurs, but secure-credential retrieval, credential validation, the connectivity probe and cache insertion are skipped; the stored string is returned with status 200 |
| Source | `DBConnectionAPI/DBConnectionAPI/Services/ConnectionManager.cs:52-87` |

---

## BR-DBC-002 Cache key format

| Field | Value |
| --- | --- |
| Title | Cache key format |
| Statement | Cache keys are `ORA.<instance>.<user>`, `SQL.<server>.<instance>.<user>` and `POSTGRES.<server>.<instance>.<user>`. They are case-sensitive and not normalized |
| Trigger | Key generation at the start of each retrieval request |
| Enforcement | `ConnectionManager` key builders |
| Origin | api |
| Reached from | [`GET /v1/Connections/SQL/{server}/{instance}/{username}`](endpoints/connections.md#get-v1-connections-sql-server-instance-username), [`GET /v1/Connections/PostgreSQL/{instance}/{username}`](endpoints/connections.md#get-v1-connections-postgresql-instance-username), [`GET /v1/Connections/ORA/{instance}/{username}`](endpoints/connections.md#get-v1-connections-ora-instance-username) |
| Outcome | Requests that differ only in letter case produce separate cache entries and separate probes |
| Source | `DBConnectionAPI/DBConnectionAPI/Services/ConnectionManager.cs:88-98` |

---

## BR-DBC-003 Guarded cache insert, first writer wins

| Field | Value |
| --- | --- |
| Title | Guarded cache insert, first writer wins |
| Statement | A value is added to the cache only under the lock, only when it is not null or whitespace, and only when the key is absent |
| Trigger | After a successful validation |
| Enforcement | `ConnectionManager.AddConnectionString` under `lock (_connectionsLock)` |
| Origin | api |
| Reached from | [`GET /v1/Connections/SQL/{server}/{instance}/{username}`](endpoints/connections.md#get-v1-connections-sql-server-instance-username), [`GET /v1/Connections/PostgreSQL/{instance}/{username}`](endpoints/connections.md#get-v1-connections-postgresql-instance-username), [`GET /v1/Connections/ORA/{instance}/{username}`](endpoints/connections.md#get-v1-connections-ora-instance-username) |
| Outcome | Duplicates and blank values are ignored; concurrent misses on one key cost redundant work but cannot corrupt the cache |
| Source | `DBConnectionAPI/DBConnectionAPI/Services/ConnectionManager.cs:37-50` |

---

## BR-DBC-004 Cache entries never expire

| Field | Value |
| --- | --- |
| Title | Cache entries never expire |
| Statement | Cached entries persist for the lifetime of the singleton. There is no expiry, eviction or invalidation |
| Trigger | Process lifetime |
| Enforcement | `IConnectionManager` registered as a singleton holding a process-wide dictionary |
| Origin | api |
| Reached from | [`GET /v1/Connections/SQL/{server}/{instance}/{username}`](endpoints/connections.md#get-v1-connections-sql-server-instance-username), [`GET /v1/Connections/PostgreSQL/{instance}/{username}`](endpoints/connections.md#get-v1-connections-postgresql-instance-username), [`GET /v1/Connections/ORA/{instance}/{username}`](endpoints/connections.md#get-v1-connections-ora-instance-username) |
| Outcome | A credential rotation takes effect only after the process restarts |
| Source | `DBConnectionAPI/DBConnectionAPI/Services/ConnectionManager.cs:16`; `DBConnectionAPI/DBConnectionAPI/Startup.cs:23` |

---

## BR-DBC-005 Secure-setting keys are lower-cased

| Field | Value |
| --- | --- |
| Title | Secure-setting keys are lower-cased |
| Statement | Secure-setting key names are lower-cased: `ora_<instance>_<user>`, `sql_<server>_<instance>_<user>` and `postgres_<instance>_<user>` |
| Trigger | Retrieval and Admin requests, when the credential key is built |
| Enforcement | `ConnectionManager` secure-setting key builders (used by `AdminController` through `IConnectionManager`) |
| Origin | api |
| Reached from | All six operations: [`GET /v1/Connections/SQL/{server}/{instance}/{username}`](endpoints/connections.md#get-v1-connections-sql-server-instance-username), [`GET /v1/Connections/PostgreSQL/{instance}/{username}`](endpoints/connections.md#get-v1-connections-postgresql-instance-username), [`GET /v1/Connections/ORA/{instance}/{username}`](endpoints/connections.md#get-v1-connections-ora-instance-username), [`POST /v1/Admin/GetOracleConnectionInfo`](endpoints/admin.md#post-v1-admin-getoracleconnectioninfo), [`POST /v1/Admin/GetPostgresConnectionInfo`](endpoints/admin.md#post-v1-admin-getpostgresconnectioninfo), [`POST /v1/Admin/GetSqlConnectionInfo`](endpoints/admin.md#post-v1-admin-getsqlconnectioninfo) |
| Outcome | Credential lookup is case-insensitive for the caller-supplied parts, while the cache key is not (see [BR-DBC-002](business-rules.md#br-dbc-002-cache-key-format)) |
| Source | `DBConnectionAPI/DBConnectionAPI/Services/ConnectionManager.cs:100-111` |

---

## BR-DBC-006 PostgreSQL credentials ignore the server

| Field | Value |
| --- | --- |
| Title | PostgreSQL credentials ignore the server |
| Statement | PostgreSQL credentials are keyed by instance and user only. The cache key does include the server |
| Trigger | PostgreSQL retrieval and Admin requests |
| Enforcement | `ConnectionManager` PostgreSQL key builders |
| Origin | api |
| Reached from | [`GET /v1/Connections/PostgreSQL/{instance}/{username}`](endpoints/connections.md#get-v1-connections-postgresql-instance-username), [`POST /v1/Admin/GetPostgresConnectionInfo`](endpoints/admin.md#post-v1-admin-getpostgresconnectioninfo) |
| Outcome | One credential is shared across all servers for an instance and user, while each server still gets its own cache entry |
| Source | `DBConnectionAPI/DBConnectionAPI/Services/ConnectionManager.cs:96-98,108-111` |

---

## BR-DBC-007 Templates come from the Environment API group

| Field | Value |
| --- | --- |
| Title | Templates come from the Environment API group |
| Statement | Connection-string templates are read from Environment API group `dbconnectionapi` (keys `sqlconnectiontemplate`, `postgresqlconnectiontemplate`, `oraconnectiontemplate`). `{SERVER}` and `{INSTANCE}` are replaced; the Oracle template replaces only `{INSTANCE}` |
| Trigger | Template retrieval for every operation |
| Enforcement | `ConnectionManager.GetSqlTemplate`, `GetPostgreSqlTemplate`, `GetOracleTemplate`; key names in `EnvironmentKeys` |
| Origin | api |
| Reached from | All six operations: [`GET /v1/Connections/SQL/{server}/{instance}/{username}`](endpoints/connections.md#get-v1-connections-sql-server-instance-username), [`GET /v1/Connections/PostgreSQL/{instance}/{username}`](endpoints/connections.md#get-v1-connections-postgresql-instance-username), [`GET /v1/Connections/ORA/{instance}/{username}`](endpoints/connections.md#get-v1-connections-ora-instance-username), [`POST /v1/Admin/GetOracleConnectionInfo`](endpoints/admin.md#post-v1-admin-getoracleconnectioninfo), [`POST /v1/Admin/GetPostgresConnectionInfo`](endpoints/admin.md#post-v1-admin-getpostgresconnectioninfo), [`POST /v1/Admin/GetSqlConnectionInfo`](endpoints/admin.md#post-v1-admin-getsqlconnectioninfo) |
| Outcome | A template with placeholders substituted. There is no null guard, so a missing template fails with an unhandled null reference (status not visible in source**) |
| Source | `DBConnectionAPI/DBConnectionAPI/Services/ConnectionManager.cs:119-133`; `DBConnectionAPI/DBConnectionAPI/EnvironmentKeys.cs:10-13` |

---

## BR-DBC-008 Credentials are substituted into the template

| Field | Value |
| --- | --- |
| Title | Credentials are substituted into the template |
| Statement | The user name and password are substituted by replacing `{USER}` and `{PW}` in the template |
| Trigger | Formatting, after the credential has been read |
| Enforcement | `ConnectionManager.FormatConnectionString` |
| Origin | api |
| Reached from | All six operations (see [BR-DBC-005](business-rules.md#br-dbc-005-secure-setting-keys-are-lower-cased) for the endpoint list) |
| Outcome | The composed connection string |
| Source | `DBConnectionAPI/DBConnectionAPI/Services/ConnectionManager.cs:134` |

---

## BR-DBC-009 Missing secure setting returns 404

| Field | Value |
| --- | --- |
| Title | Missing secure setting returns 404 |
| Statement | If the secure setting does not exist, the request fails with HTTP 404. The message names the group, the key and the Environment API track |
| Trigger | `HasSetting` returns false |
| Enforcement | `ConnectionManager.RetrieveConnection`, throwing `ConnectionManagerException(404)` |
| Origin | api |
| Reached from | [`GET /v1/Connections/SQL/{server}/{instance}/{username}`](endpoints/connections.md#get-v1-connections-sql-server-instance-username), [`GET /v1/Connections/PostgreSQL/{instance}/{username}`](endpoints/connections.md#get-v1-connections-postgresql-instance-username), [`GET /v1/Connections/ORA/{instance}/{username}`](endpoints/connections.md#get-v1-connections-ora-instance-username) |
| Outcome | 404 produced by the package error-handling middleware (`DynamicErrorHandler`†) from the typed exception; the message discloses the key name and track |
| Source | `DBConnectionAPI/DBConnectionAPI/Services/ConnectionManager.cs:141,189`; `DBConnectionAPI/DBConnectionAPI/Exceptions/ConnectionManagerException.cs:9-12` |

---

## BR-DBC-010 Credential split at the first pipe, unguarded

| Field | Value |
| --- | --- |
| Title | Credential split at the first pipe, unguarded |
| Statement | The secure credential is split at the first pipe character: the user name is before it and the password after it. No guard exists |
| Trigger | Retrieval after `HasSetting` succeeds |
| Enforcement | `ConnectionManager.RetrieveConnection` |
| Origin | api |
| Reached from | [`GET /v1/Connections/SQL/{server}/{instance}/{username}`](endpoints/connections.md#get-v1-connections-sql-server-instance-username), [`GET /v1/Connections/PostgreSQL/{instance}/{username}`](endpoints/connections.md#get-v1-connections-postgresql-instance-username), [`GET /v1/Connections/ORA/{instance}/{username}`](endpoints/connections.md#get-v1-connections-ora-instance-username) |
| Outcome | A missing pipe raises `ArgumentOutOfRangeException` outside the retry `try`; it is not a `ConnectionManagerException`, so the status depends on the package middleware (presumably 500**) |
| Source | `DBConnectionAPI/DBConnectionAPI/Services/ConnectionManager.cs:145-148` |

---

## BR-DBC-011 Backoff list must cover the retry attempts

| Field | Value |
| --- | --- |
| Title | Backoff list must cover the retry attempts |
| Statement | The backoff delay list must have at least `MaxConnectionTestAttempts - 1` entries |
| Trigger | Before probing |
| Enforcement | `ConnectionManager.ValidateBackoffConfiguration` |
| Origin | api |
| Reached from | [`GET /v1/Connections/SQL/{server}/{instance}/{username}`](endpoints/connections.md#get-v1-connections-sql-server-instance-username), [`GET /v1/Connections/PostgreSQL/{instance}/{username}`](endpoints/connections.md#get-v1-connections-postgresql-instance-username), [`GET /v1/Connections/ORA/{instance}/{username}`](endpoints/connections.md#get-v1-connections-ora-instance-username) |
| Outcome | Otherwise `InvalidOperationException` (a configuration guard against a subclass change; status not visible in source**) |
| Source | `DBConnectionAPI/DBConnectionAPI/Services/ConnectionManager.cs:222-230` |

---

## BR-DBC-012 Four probe attempts with 2, 3 and 5 second backoff

| Field | Value |
| --- | --- |
| Title | Four probe attempts with 2, 3 and 5 second backoff |
| Statement | The connectivity probe is attempted up to 4 times, waiting 2 s, 3 s and 5 s between attempts. The wait is a blocking delay |
| Trigger | Probe failure |
| Enforcement | `ConnectionManager.RetrieveConnection` retry loop; delay helper using `Thread.Sleep` |
| Origin | api |
| Reached from | [`GET /v1/Connections/SQL/{server}/{instance}/{username}`](endpoints/connections.md#get-v1-connections-sql-server-instance-username), [`GET /v1/Connections/PostgreSQL/{instance}/{username}`](endpoints/connections.md#get-v1-connections-postgresql-instance-username), [`GET /v1/Connections/ORA/{instance}/{username}`](endpoints/connections.md#get-v1-connections-ora-instance-username) |
| Outcome | Transient failures recover; a failing key adds up to about 10 s of latency on the request thread. PostgreSQL never fails the probe (see [BR-DBC-020](business-rules.md#br-dbc-020-postgresql-strings-are-never-probed)) |
| Source | `DBConnectionAPI/DBConnectionAPI/Services/ConnectionManager.cs:153-185,197-215` |

---

## BR-DBC-013 Permanent errors stop retrying

| Field | Value |
| --- | --- |
| Title | Permanent errors stop retrying |
| Statement | Retrying stops immediately for `ORA-12154`, `ORA-12504`, `TNS-12154`, `ORA-01017`, `Login failed` and `ORA-01000` |
| Trigger | Exception message match inside the retry loop |
| Enforcement | `ConnectionManager.RetrieveConnection` (permanent-error check) |
| Origin | api |
| Reached from | [`GET /v1/Connections/SQL/{server}/{instance}/{username}`](endpoints/connections.md#get-v1-connections-sql-server-instance-username), [`GET /v1/Connections/ORA/{instance}/{username}`](endpoints/connections.md#get-v1-connections-ora-instance-username) |
| Outcome | The loop breaks and the final error is mapped by [BR-DBC-015](business-rules.md#br-dbc-015-tns-alias-errors-map-to-400) and [BR-DBC-016](business-rules.md#br-dbc-016-other-final-failures-map-to-500) |
| Source | `DBConnectionAPI/DBConnectionAPI/Services/ConnectionManager.cs:176-180,257-265` |

---

## BR-DBC-014 A false probe result is a failure

| Field | Value |
| --- | --- |
| Title | A false probe result is a failure |
| Statement | A probe that returns false counts as a failure with status 500 ("did not return a successful result") |
| Trigger | The probe delegate returns false |
| Enforcement | `ConnectionManager.RetrieveConnection` |
| Origin | api |
| Reached from | [`GET /v1/Connections/SQL/{server}/{instance}/{username}`](endpoints/connections.md#get-v1-connections-sql-server-instance-username), [`GET /v1/Connections/ORA/{instance}/{username}`](endpoints/connections.md#get-v1-connections-ora-instance-username) |
| Outcome | The attempt is retried; after the attempts run out the error is mapped (see [BR-DBC-017](business-rules.md#br-dbc-017-an-existing-typed-exception-keeps-its-status)) |
| Source | `DBConnectionAPI/DBConnectionAPI/Services/ConnectionManager.cs:160` |

---

## BR-DBC-015 TNS alias errors map to 400

| Field | Value |
| --- | --- |
| Title | TNS alias errors map to 400 |
| Statement | TNS alias errors (`ORA-12154`, `ORA-12504`, `TNS-12154`) map to HTTP 400. The message cites the alias and the track |
| Trigger | Final failure after the retry loop |
| Enforcement | `ConnectionManager.BuildConnectionManagerException` |
| Origin | api |
| Reached from | [`GET /v1/Connections/ORA/{instance}/{username}`](endpoints/connections.md#get-v1-connections-ora-instance-username); possible from [`GET /v1/Connections/SQL/{server}/{instance}/{username}`](endpoints/connections.md#get-v1-connections-sql-server-instance-username)* |
| Outcome | `ConnectionManagerException(400)` rendered by the package middleware (`DynamicErrorHandler`†) |
| Source | `DBConnectionAPI/DBConnectionAPI/Services/ConnectionManager.cs:232-255` |

---

## BR-DBC-016 Other final failures map to 500

| Field | Value |
| --- | --- |
| Title | Other final failures map to 500 |
| Statement | Other final failures map to HTTP 500: `ORA-01034` (database unavailable), `ORA-01000` (cursors), `ORA-01017` or `Login failed` (invalid credentials; the message names the secure-setting key and track), `ORA-12570`, `ORA-12571` and `ORA-03135` (network interrupted; includes the raw message), and a generic fallback (Something went wrong, then See: and the raw message {msg}) |
| Trigger | Final failure after the retry loop |
| Enforcement | `ConnectionManager.BuildConnectionManagerException` |
| Origin | api |
| Reached from | [`GET /v1/Connections/SQL/{server}/{instance}/{username}`](endpoints/connections.md#get-v1-connections-sql-server-instance-username), [`GET /v1/Connections/ORA/{instance}/{username}`](endpoints/connections.md#get-v1-connections-ora-instance-username) |
| Outcome | `ConnectionManagerException(500)` rendered by the package middleware (`DynamicErrorHandler`†); the body can disclose key names, the track and raw provider messages |
| Source | `DBConnectionAPI/DBConnectionAPI/Services/ConnectionManager.cs:232-255` |

---

## BR-DBC-017 An existing typed exception keeps its status

| Field | Value |
| --- | --- |
| Title | An existing typed exception keeps its status |
| Statement | If the last exception is already a `ConnectionManagerException`, it is rethrown unchanged and its status is kept |
| Trigger | Final failure after the retry loop |
| Enforcement | `ConnectionManager.BuildConnectionManagerException` |
| Origin | api |
| Reached from | [`GET /v1/Connections/SQL/{server}/{instance}/{username}`](endpoints/connections.md#get-v1-connections-sql-server-instance-username), [`GET /v1/Connections/ORA/{instance}/{username}`](endpoints/connections.md#get-v1-connections-ora-instance-username) |
| Outcome | For example, a probe that returned false stays a 500 (see [BR-DBC-014](business-rules.md#br-dbc-014-a-false-probe-result-is-a-failure)) |
| Source | `DBConnectionAPI/DBConnectionAPI/Services/ConnectionManager.cs:232-255` |

---

## BR-DBC-018 Probe validity definition

| Field | Value |
| --- | --- |
| Title | Probe validity definition |
| Statement | Oracle validity means `SELECT 1 FROM DUAL` returns non-null. SQL Server validity means `SELECT 1` returns non-null. A null unit of work means invalid |
| Trigger | Each probe attempt |
| Enforcement | `ConnectionManager.TestOracleConnection`, `TestSqlConnection` through `IUnitOfWorkFactory` (`SAIF.NetCore.NHibernate`) |
| Origin | api |
| Reached from | [`GET /v1/Connections/ORA/{instance}/{username}`](endpoints/connections.md#get-v1-connections-ora-instance-username), [`GET /v1/Connections/SQL/{server}/{instance}/{username}`](endpoints/connections.md#get-v1-connections-sql-server-instance-username) |
| Outcome | A boolean validity result; the scalar value itself is never inspected |
| Source | `DBConnectionAPI/DBConnectionAPI/Services/ConnectionManager.cs:267-305` |

---

## BR-DBC-019 Legacy token decoded for the probe only

| Field | Value |
| --- | --- |
| Title | Legacy token decoded for the probe only |
| Statement | Before probing, the legacy token `ZSPECIALCHAR` is replaced with the BEL control character (char 7). This applies only to the string under test; the cached and returned string keeps the token |
| Trigger | Each probe attempt |
| Enforcement | `ConnectionManager.HandleLegacyConnectionString` |
| Origin | api |
| Reached from | [`GET /v1/Connections/SQL/{server}/{instance}/{username}`](endpoints/connections.md#get-v1-connections-sql-server-instance-username), [`GET /v1/Connections/PostgreSQL/{instance}/{username}`](endpoints/connections.md#get-v1-connections-postgresql-instance-username), [`GET /v1/Connections/ORA/{instance}/{username}`](endpoints/connections.md#get-v1-connections-ora-instance-username) |
| Outcome | The probe connects with the decoded string; callers receive the raw token and must decode it themselves* |
| Source | `DBConnectionAPI/DBConnectionAPI/Services/ConnectionManager.cs:157,312-316` |

---

## BR-DBC-020 PostgreSQL strings are never probed

| Field | Value |
| --- | --- |
| Title | PostgreSQL strings are never probed |
| Statement | PostgreSQL connection strings are never probed. The test always returns true |
| Trigger | PostgreSQL retrieval on a cache miss |
| Enforcement | `ConnectionManager.TestPostgreSqlConnection` |
| Origin | api |
| Reached from | [`GET /v1/Connections/PostgreSQL/{instance}/{username}`](endpoints/connections.md#get-v1-connections-postgresql-instance-username) |
| Outcome | The string is cached and returned without validation |
| Source | `DBConnectionAPI/DBConnectionAPI/Services/ConnectionManager.cs:307-311` |

---

## BR-DBC-021 Blank PostgreSQL server becomes a placeholder

| Field | Value |
| --- | --- |
| Title | Blank PostgreSQL server becomes a placeholder |
| Statement | When `serverName` is null or whitespace, the server value is set to the literal placeholder `{SERVER}` |
| Trigger | PostgreSQL request without the optional `serverName` query value |
| Enforcement | `ConnectionsController.GetPostgreSqlConnectionString` (action body) |
| Origin | api |
| Reached from | [`GET /v1/Connections/PostgreSQL/{instance}/{username}`](endpoints/connections.md#get-v1-connections-postgresql-instance-username) |
| Outcome | The returned string and the cache key keep `{SERVER}` for the caller to fill in |
| Source | `DBConnectionAPI/DBConnectionAPI/Controllers/ConnectionsController.cs:40-41` |

---

## BR-DBC-022 Retrieval requests are audit-logged

| Field | Value |
| --- | --- |
| Title | Retrieval requests are audit-logged |
| Statement | Every retrieval request is logged at Info level with the key components, the caller's `UserId` and `AuthenticationScheme`, and the timestamp. Admin requests are not logged |
| Trigger | Each Connections request |
| Enforcement | `ConnectionsController` actions through `ISaifLogService` and `IIdentityServiceAccessor` |
| Origin | api |
| Reached from | [`GET /v1/Connections/SQL/{server}/{instance}/{username}`](endpoints/connections.md#get-v1-connections-sql-server-instance-username), [`GET /v1/Connections/PostgreSQL/{instance}/{username}`](endpoints/connections.md#get-v1-connections-postgresql-instance-username), [`GET /v1/Connections/ORA/{instance}/{username}`](endpoints/connections.md#get-v1-connections-ora-instance-username) |
| Outcome | An audit trail for retrievals only; the identity is used for logging, never for an authorization decision |
| Source | `DBConnectionAPI/DBConnectionAPI/Controllers/ConnectionsController.cs:32,43,51` |

---

## BR-DBC-023 Admin inspection discloses raw credentials

| Field | Value |
| --- | --- |
| Title | Admin inspection discloses raw credentials |
| Statement | Admin inspection reads the secure setting directly. It skips the `HasSetting` check, the probe and the cache, and returns the raw `credentials`, `user`, `pw` and `connectionString` |
| Trigger | Each Admin request |
| Enforcement | `AdminController.GetConnectionInfo` |
| Origin | api |
| Reached from | [`POST /v1/Admin/GetOracleConnectionInfo`](endpoints/admin.md#post-v1-admin-getoracleconnectioninfo), [`POST /v1/Admin/GetPostgresConnectionInfo`](endpoints/admin.md#post-v1-admin-getpostgresconnectioninfo), [`POST /v1/Admin/GetSqlConnectionInfo`](endpoints/admin.md#post-v1-admin-getsqlconnectioninfo) |
| Outcome | Plaintext credential disclosure with status 200. A missing secure setting is not handled locally, so the failure is raised by the package client and its status is not visible in source** |
| Source | `DBConnectionAPI/DBConnectionAPI/Controllers/AdminController.cs:48-70` |

---

## BR-DBC-024 Admin split failure reported with status 200

| Field | Value |
| --- | --- |
| Title | Admin split failure reported with status 200 |
| Statement | If the Admin credential split fails, the response adds an `Error` entry (pipe delimiter expected), returns an empty user and password, and keeps HTTP 200 |
| Trigger | Exception while splitting the credential |
| Enforcement | Bare `try/catch` in `AdminController.GetConnectionInfo` |
| Origin | api |
| Reached from | [`POST /v1/Admin/GetOracleConnectionInfo`](endpoints/admin.md#post-v1-admin-getoracleconnectioninfo), [`POST /v1/Admin/GetPostgresConnectionInfo`](endpoints/admin.md#post-v1-admin-getpostgresconnectioninfo), [`POST /v1/Admin/GetSqlConnectionInfo`](endpoints/admin.md#post-v1-admin-getsqlconnectioninfo) |
| Outcome | The error is reported in the body while the status stays 200; the raw `credentials` value is still returned |
| Source | `DBConnectionAPI/DBConnectionAPI/Controllers/AdminController.cs:54-63` |

---

## BR-DBC-025 Resource authorization on every endpoint

| Field | Value |
| --- | --- |
| Title | Resource authorization on every endpoint |
| Statement | All endpoints require resource `DBConnAPIRetrieveResource`. The caller identity name must be non-blank, the domain prefix is stripped, and the security service authorization check is called† |
| Trigger | Every request, before the action runs |
| Enforcement | `[SecurityApiAuthorize("DBConnAPIRetrieveResource")]` on both controllers; handler behavior from `SAIF.NetCore.ServiceExtensions`† |
| Origin | inherited |
| Reached from | All six operations: [`GET /v1/Connections/SQL/{server}/{instance}/{username}`](endpoints/connections.md#get-v1-connections-sql-server-instance-username), [`GET /v1/Connections/PostgreSQL/{instance}/{username}`](endpoints/connections.md#get-v1-connections-postgresql-instance-username), [`GET /v1/Connections/ORA/{instance}/{username}`](endpoints/connections.md#get-v1-connections-ora-instance-username), [`POST /v1/Admin/GetOracleConnectionInfo`](endpoints/admin.md#post-v1-admin-getoracleconnectioninfo), [`POST /v1/Admin/GetPostgresConnectionInfo`](endpoints/admin.md#post-v1-admin-getpostgresconnectioninfo), [`POST /v1/Admin/GetSqlConnectionInfo`](endpoints/admin.md#post-v1-admin-getsqlconnectioninfo) |
| Outcome | Denied requests receive a status the package documentation does not state** (401 or 403 by ASP.NET Core convention*). The same resource guards the Admin and the retrieval endpoints, and no data-level check ties the caller to the requested instance or user |
| Source | `DBConnectionAPI/DBConnectionAPI/Controllers/ConnectionsController.cs:13`; `DBConnectionAPI/DBConnectionAPI/Controllers/AdminController.cs:12` |

---

## Related documentation
- [API index](index.md)
- [Overview](overview.md)
- [Architecture](architecture.md)
- [Hosting pipeline](hosting-pipeline.md)
- [Endpoints](endpoints.md)
- [Authentication and authorization](authentication-authorization.md)
- [Dependencies](dependencies.md)
- [Configuration](configuration.md)
- [Operations and processes](operations-processes.md)
- [Data model](data-model.md)
- [Operational runbook](operational-runbook.md)
- [Tech debt](tech-debt.md)

> **Note:** Statements marked with `†` are derived from dependency library
> documentation via the Legends CLI. Statements marked with `*` were inferred
> from code structure or contextual evidence. Statements marked with `**` could
> not be confirmed; treat as unknown until verified.

## Sources
- DBConnectionAPI/DBConnectionAPI/Services/ConnectionManager.cs
- DBConnectionAPI/DBConnectionAPI/Services/IConnectionManager.cs
- DBConnectionAPI/DBConnectionAPI/Controllers/ConnectionsController.cs
- DBConnectionAPI/DBConnectionAPI/Controllers/AdminController.cs
- DBConnectionAPI/DBConnectionAPI/Exceptions/ConnectionManagerException.cs
- DBConnectionAPI/DBConnectionAPI/EnvironmentKeys.cs
- DBConnectionAPI/DBConnectionAPI/Startup.cs
